import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.*;
import java.io.*;

public class MazeGame extends JFrame {

    private static final int TILE_SIZE = 30;
    private static final int ROWS = 15;
    private static final int COLS = 15;
    private static final int GAME_DURATION = 60;
    private static final int BASE_ENEMY_SPEED = 500;
    private static final int MIN_ENEMY_SPEED = 150;
    private static final int INFO_PANEL_WIDTH = 200;
    private static final int EXIT_SCORE = 20;
    private static final String HIGH_SCORE_FILE_PREFIX = "maze_highscore_";

    private char[][] maze;
    private Point playerPos;
    private Point enemyPos;
    private Point itemPos;
    private int score = 0;
    private int highScoreChase = 0;
    private int highScoreFree = 0;
    private long endTime;
    private boolean gameRunning = false;
    private int enemySpeed = BASE_ENEMY_SPEED;
    private boolean chaseMode = true;

    private GamePanel gamePanel;
    private ScheduledExecutorService executor;
    private ScheduledFuture<?> enemyMoveTask;
    private long pauseTime;

    public MazeGame() {
        initUI();
        loadHighScores();
        showStartMenu();
    }

    private void initUI() {
        setTitle("迷宫探险家:高分挑战");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setResizable(false);
        gamePanel = new GamePanel();
    }

    private void loadHighScores() {
        highScoreChase = loadHighScore("chase");
        highScoreFree = loadHighScore("free");
    }

    private int loadHighScore(String mode) {
        String filename = HIGH_SCORE_FILE_PREFIX + mode + ".dat";
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename))) {
            return ois.readInt();
        } catch (FileNotFoundException e) {
            return 0;
        } catch (IOException e) {
            System.out.println("读取最高分失败(" + mode + "): " + e.getMessage());
            return 0;
        }
    }

    private void saveHighScore(String mode, int score) {
        String filename = HIGH_SCORE_FILE_PREFIX + mode + ".dat";
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename))) {
            oos.writeInt(score);
        } catch (IOException e) {
            System.out.println("保存最高分失败(" + mode + "): " + e.getMessage());
        }
    }

    private void showStartMenu() {
        JPanel menuPanel = new JPanel(new BorderLayout());
        menuPanel.setPreferredSize(new Dimension(400, 300));
        menuPanel.setBackground(new Color(240, 240, 240));

        JLabel titleLabel = new JLabel("迷宫探险家:高分挑战", JLabel.CENTER);
        titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 32));
        titleLabel.setBorder(BorderFactory.createEmptyBorder(20, 0, 30, 0));

        JPanel buttonPanel = new JPanel(new GridLayout(3, 1, 0, 15));
        buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 100, 50, 100));
        buttonPanel.setBackground(new Color(240, 240, 240));

        JButton chaseModeButton = createModeButton("追逐模式(有敌人)", true);
        JButton freeModeButton = createModeButton("自由模式(无敌人)", false);

        JLabel highScoreLabel = new JLabel(
                String.format("最高分 - 追逐: %d  自由: %d", highScoreChase, highScoreFree),
                JLabel.CENTER
        );
        highScoreLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));

        buttonPanel.add(chaseModeButton);
        buttonPanel.add(freeModeButton);
        buttonPanel.add(highScoreLabel);

        menuPanel.add(titleLabel, BorderLayout.NORTH);
        menuPanel.add(buttonPanel, BorderLayout.CENTER);

        getContentPane().add(menuPanel);
        pack();
        setLocationRelativeTo(null);
        setVisible(true);
    }

    private JButton createModeButton(String text, boolean isChaseMode) {
        JButton button = new JButton(text);
        button.setFont(new Font("微软雅黑", Font.PLAIN, 18));
        button.setBackground(isChaseMode ? new Color(220, 240, 255) : new Color(220, 255, 220));
        button.addActionListener(e -> {
            chaseMode = isChaseMode;
            getContentPane().removeAll();
            initializeGame();
        });
        return button;
    }

    private void initializeGame() {
        initGameState();
        setupUI();
        startGameLoop();
    }

    private void initGameState() {
        maze = generateMultiPathMaze();
        placeCharacters();
        score = 0;
        endTime = System.currentTimeMillis() + GAME_DURATION * 1000L;
        gameRunning = true;
        enemySpeed = BASE_ENEMY_SPEED;
    }

    private char[][] generateMultiPathMaze() {
        char[][] maze = new char[ROWS][COLS];
        for (int i = 0; i < ROWS; i++) {
            Arrays.fill(maze[i], '#');
        }

        Random rand = new Random();
        List<Point> walls = new ArrayList<>();
        Point start = new Point(1, 1);
        maze[start.y][start.x] = ' ';
        addWalls(start, walls, maze);

        while (!walls.isEmpty()) {
            Point wall = walls.remove(rand.nextInt(walls.size()));
            int x = wall.x, y = wall.y;

            if (x % 2 == 1 && y % 2 == 0) {
                if (maze[y-1][x] != maze[y+1][x]) {
                    maze[y][x] = ' ';
                    Point newCell = maze[y-1][x] == ' ' ? new Point(x, y+1) : new Point(x, y-1);
                    maze[newCell.y][newCell.x] = ' ';
                    addWalls(newCell, walls, maze);
                }
            } else if (x % 2 == 0 && y % 2 == 1) {
                if (maze[y][x-1] != maze[y][x+1]) {
                    maze[y][x] = ' ';
                    Point newCell = maze[y][x-1] == ' ' ? new Point(x+1, y) : new Point(x-1, y);
                    maze[newCell.y][newCell.x] = ' ';
                    addWalls(newCell, walls, maze);
                }
            }
        }

        addExtraPaths(maze, rand);
        maze[0][1] = ' ';
        maze[ROWS-1][COLS-2] = ' ';
        return maze;
    }

    private void addWalls(Point cell, List<Point> walls, char[][] maze) {
        int[][] directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
        for (int[] dir : directions) {
            int nx = cell.x + dir[0], ny = cell.y + dir[1];
            if (nx > 0 && nx < COLS-1 && ny > 0 && ny < ROWS-1 && maze[ny][nx] == '#') {
                walls.add(new Point(nx, ny));
            }
        }
    }

    private void addExtraPaths(char[][] maze, Random rand) {
        int extraPaths = ROWS * COLS / 10;
        while (extraPaths > 0) {
            int x = rand.nextInt(COLS-2) + 1, y = rand.nextInt(ROWS-2) + 1;
            if (maze[y][x] == '#') {
                if (x % 2 == 1 && y % 2 == 0 && maze[y-1][x] == ' ' && maze[y+1][x] == ' ') {
                    maze[y][x] = ' ';
                    extraPaths--;
                } else if (x % 2 == 0 && y % 2 == 1 && maze[y][x-1] == ' ' && maze[y][x+1] == ' ') {
                    maze[y][x] = ' ';
                    extraPaths--;
                }
            }
        }
    }

    private void placeCharacters() {
        playerPos = new Point(1, 0);

        Random rand = new Random();
        List<Point> farPositions = new ArrayList<>();
        for (int y = 1; y < ROWS-1; y++) {
            for (int x = 1; x < COLS-1; x++) {
                if (maze[y][x] == ' ' && Math.sqrt(Math.pow(x-playerPos.x,2)+Math.pow(y-playerPos.y,2)) > Math.min(ROWS,COLS)/2) {
                    farPositions.add(new Point(x,y));
                }
            }
        }

        enemyPos = !farPositions.isEmpty() ?
                farPositions.get(rand.nextInt(farPositions.size())) :
                findRandomPosition(rand, playerPos);

        List<Point> middlePositions = new ArrayList<>();
        int centerX = COLS/2, centerY = ROWS/2;
        for (int y = 1; y < ROWS-1; y++) {
            for (int x = 1; x < COLS-1; x++) {
                if (maze[y][x] == ' ' && Math.abs(x-centerX) <= 3 && Math.abs(y-centerY) <= 3) {
                    middlePositions.add(new Point(x,y));
                }
            }
        }

        itemPos = !middlePositions.isEmpty() ?
                middlePositions.get(rand.nextInt(middlePositions.size())) :
                findRandomPosition(rand, playerPos, enemyPos);
    }

    private Point findRandomPosition(Random rand, Point... exclude) {
        Point pos = null;
        Point finalPos = pos;
        do {
            pos = new Point(rand.nextInt(COLS-2)+1, rand.nextInt(ROWS-2)+1);
        } while (maze[pos.y][pos.x] != ' ' || Arrays.stream(exclude).anyMatch(p -> p != null && p.equals(finalPos)));
        return pos;
    }

    private void setupUI() {
        setSize(COLS * TILE_SIZE + INFO_PANEL_WIDTH, (int)(ROWS * TILE_SIZE * 1.08));
        add(gamePanel);
        setLocationRelativeTo(null);
        addKeyListener(new GameKeyListener());
        requestFocusInWindow();
    }

    private void startGameLoop() {
        executor = Executors.newScheduledThreadPool(3);
        if (chaseMode) {
            startEnemyMoveTask();
        }
        executor.scheduleAtFixedRate(this::checkGameTime, 0, 100, TimeUnit.MILLISECONDS);
        executor.scheduleAtFixedRate(gamePanel::repaint, 0, 16, TimeUnit.MILLISECONDS);
        setVisible(true);
    }

    private class GameKeyListener extends KeyAdapter {
        @Override
        public void keyPressed(KeyEvent e) {
            if (e.getKeyCode() == KeyEvent.VK_P) {
                togglePause();
                return;
            }

            if (gameRunning) {
                switch (e.getKeyCode()) {
                    case KeyEvent.VK_UP: movePlayer(0, -1); break;
                    case KeyEvent.VK_DOWN: movePlayer(0, 1); break;
                    case KeyEvent.VK_LEFT: movePlayer(-1, 0); break;
                    case KeyEvent.VK_RIGHT: movePlayer(1, 0); break;
                }
            }
        }
    }

    private void movePlayer(int dx, int dy) {
        Point newPos = new Point(playerPos.x + dx, playerPos.y + dy);
        if (isValidMove(newPos)) {
            playerPos = newPos;
            checkCollisions();
        }
    }

    private boolean isValidMove(Point pos) {
        return pos.x >= 0 && pos.x < COLS &&
                pos.y >= 0 && pos.y < ROWS &&
                maze[pos.y][pos.x] != '#';
    }

    private void togglePause() {
        gameRunning = !gameRunning;
        if (gameRunning) {

            long pausedDuration = System.currentTimeMillis() - pauseTime;
            endTime += pausedDuration;
            if (chaseMode) startEnemyMoveTask();
        } else {

            pauseTime = System.currentTimeMillis();
            if (chaseMode && enemyMoveTask != null) {
                enemyMoveTask.cancel(false);
            }
        }
    }

    private void startEnemyMoveTask() {
        if (enemyMoveTask != null) enemyMoveTask.cancel(false);
        enemyMoveTask = executor.scheduleAtFixedRate(this::moveEnemy, 0, enemySpeed, TimeUnit.MILLISECONDS);
    }

    private void moveEnemy() {
        if (!gameRunning) return;

        updateEnemySpeed();
        Point newPos = calculateEnemyMove();
        if (isValidMove(newPos)) {
            enemyPos = newPos;
            checkCollisions();
        }
    }

    private Point calculateEnemyMove() {
        Random rand = new Random();
        Point newPos = new Point(enemyPos);
        double aggressiveness = 0.5 + 0.4 * (1.0 - (double)(enemySpeed-MIN_ENEMY_SPEED)/(BASE_ENEMY_SPEED-MIN_ENEMY_SPEED));

        if (rand.nextDouble() < aggressiveness) {
            List<Point> possibleMoves = getPossibleEnemyMoves();
            if (!possibleMoves.isEmpty()) {
                possibleMoves.sort(Comparator.comparingDouble(p -> p.distance(playerPos)));
                int choice = Math.min(possibleMoves.size()-1,
                        (int)(rand.nextDouble()*rand.nextDouble()*possibleMoves.size()));
                newPos = possibleMoves.get(choice);
            }
        }
        return newPos;
    }

    private List<Point> getPossibleEnemyMoves() {
        List<Point> moves = new ArrayList<>();
        int[][] directions = {{0,1},{1,0},{0,-1},{-1,0}};
        for (int[] dir : directions) {
            Point testPos = new Point(enemyPos.x+dir[0], enemyPos.y+dir[1]);
            if (isValidMove(testPos) && !wouldBlockPlayer(testPos)) {
                moves.add(testPos);
            }
        }
        return moves;
    }

    private boolean wouldBlockPlayer(Point enemyNewPos) {
        Point originalPos = enemyPos;
        enemyPos = enemyNewPos;

        boolean[][] visited = new boolean[ROWS][COLS];
        Queue<Point> queue = new LinkedList<>();
        queue.add(playerPos);
        visited[playerPos.y][playerPos.x] = true;

        boolean reachable = false;
        int[][] directions = {{0,1},{1,0},{0,-1},{-1,0}};

        while (!queue.isEmpty()) {
            Point current = queue.poll();
            if (current.x == COLS-2 && current.y == ROWS-1) {
                reachable = true;
                break;
            }

            for (int[] dir : directions) {
                int nx = current.x+dir[0], ny = current.y+dir[1];
                if (nx >= 0 && nx < COLS && ny >= 0 && ny < ROWS &&
                        !visited[ny][nx] && maze[ny][nx] != '#' &&
                        !(nx == enemyPos.x && ny == enemyPos.y)) {
                    visited[ny][nx] = true;
                    queue.add(new Point(nx,ny));
                }
            }
        }

        enemyPos = originalPos;
        return !reachable;
    }

    private void updateEnemySpeed() {
        long remaining = endTime - System.currentTimeMillis();
        double progress = 1.0 - Math.max(0, Math.min(1, (double)remaining / (GAME_DURATION * 1000)));
        int newSpeed = (int)(BASE_ENEMY_SPEED - (BASE_ENEMY_SPEED - MIN_ENEMY_SPEED) * progress);

        if (newSpeed != enemySpeed) {
            enemySpeed = newSpeed;
            startEnemyMoveTask();
        }
    }

    private void checkGameTime() {
        if (!gameRunning) return;

        if (System.currentTimeMillis() >= endTime) {
            gameOver("时间到! 最终得分: " + score);
        }
    }
    private void checkCollisions() {
        if (chaseMode && playerPos.equals(enemyPos)) {
            gameOver("被敌人抓住了! 最终得分: " + score);
            return;
        }

        if (playerPos.equals(itemPos)) {
            score += 10;
            itemPos = findRandomPosition(new Random(), playerPos, enemyPos);
        }

        if (playerPos.x == COLS-2 && playerPos.y == ROWS-1) {
            score += EXIT_SCORE;
            updateHighScore();
            gameOver("恭喜你逃出迷宫! 最终得分: " + score);
        }
    }

    private void updateHighScore() {
        if (chaseMode) {
            if (score > highScoreChase) {
                highScoreChase = score;
                saveHighScore("chase", highScoreChase);
            }
        } else {
            if (score > highScoreFree) {
                highScoreFree = score;
                saveHighScore("free", highScoreFree);
            }
        }
    }

    private void gameOver(String message) {
        gameRunning = false;
        executor.shutdown();
        String highScoreMsg = chaseMode ?
                String.format("追逐模式最高分: %d", highScoreChase) :
                String.format("自由模式最高分: %d", highScoreFree);
        JOptionPane.showMessageDialog(this, message + "\n" + highScoreMsg);
        dispose();
        new MazeGame();
    }

    private class GamePanel extends JPanel {
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            drawMaze(g);
            drawExit(g);
            drawItems(g);
            if (chaseMode) drawEnemy(g);
            drawPlayer(g);
            drawInfoPanel(g);
        }

        private void drawMaze(Graphics g) {
            for (int y = 0; y < ROWS; y++) {
                for (int x = 0; x < COLS; x++) {
                    g.setColor(maze[y][x] == '#' ? new Color(70,70,90) : new Color(230,230,255));
                    g.fillRect(x*TILE_SIZE, y*TILE_SIZE, TILE_SIZE, TILE_SIZE);
                }
            }
        }

        private void drawExit(Graphics g) {
            int exitX = (COLS-2) * TILE_SIZE;
            int exitY = (ROWS-1) * TILE_SIZE;
            g.setColor(new Color(50, 200, 50));
            g.fillRect(exitX, exitY, TILE_SIZE, TILE_SIZE);
            g.setColor(Color.BLACK);
            g.drawRect(exitX, exitY, TILE_SIZE, TILE_SIZE);
            g.setFont(new Font("微软雅黑", Font.BOLD, 12));
            g.drawString("出口", exitX + 5, exitY + 20);
        }

        private void drawItems(Graphics g) {
            g.setColor(Color.YELLOW);
            g.fillOval(itemPos.x*TILE_SIZE, itemPos.y*TILE_SIZE, TILE_SIZE, TILE_SIZE);
        }

        private void drawEnemy(Graphics g) {
            g.setColor(Color.RED);
            g.fillRect(enemyPos.x*TILE_SIZE, enemyPos.y*TILE_SIZE, TILE_SIZE, TILE_SIZE);
        }

        private void drawPlayer(Graphics g) {
            g.setColor(Color.BLUE);
            g.fillOval(playerPos.x*TILE_SIZE, playerPos.y*TILE_SIZE, TILE_SIZE, TILE_SIZE);
        }

        private void drawInfoPanel(Graphics g) {
            int panelX = COLS * TILE_SIZE;
            g.setColor(new Color(240,240,240));
            g.fillRect(panelX, 0, INFO_PANEL_WIDTH, ROWS*TILE_SIZE);

            Font boldFont = new Font("微软雅黑", Font.BOLD, 16);
            Font plainFont = new Font("微软雅黑", Font.PLAIN, 14);
            g.setFont(boldFont);

            // 当前分数
            g.setColor(Color.BLACK);
            g.drawString("当前分数: " + score, panelX + 20, 30);

            // 最高分
            g.setColor(new Color(0, 100, 0));
            g.drawString("最高分: " + (chaseMode ? highScoreChase : highScoreFree), panelX + 20, 60);
            int remaining = 0;
            if (gameRunning) {
                remaining = (int)Math.max(0, (endTime - System.currentTimeMillis()) / 1000);
            } else if (pauseTime > 0) {  // 游戏暂停时
                remaining = (int)Math.max(0, (endTime - pauseTime) / 1000);
            }  // 游戏结束时 remaining 保持为 0

            g.setColor(remaining < 10 ? Color.RED : Color.BLACK);
            g.drawString("时间: " + remaining + "秒", panelX + 20, 90);

            // 难度
            if (chaseMode) {
                int difficulty = (int)(100*(1.0-(double)(enemySpeed-MIN_ENEMY_SPEED)/(BASE_ENEMY_SPEED-MIN_ENEMY_SPEED)));
                g.drawString("难度: " + difficulty + "%", panelX + 20, 120);
            }

            // 操作说明
            g.setFont(plainFont);
            String[] instructions = {
                    "操作说明:",
                    "方向键 - 移动",
                    "P键 - 暂停",
                    "黄点 - 得分+10",
                    "出口 - 得分+" + EXIT_SCORE,
                    chaseMode ? "红敌人 - 避开" : ""
            };

            int yPos = 160;
            for (String instruction : instructions) {
                if (!instruction.isEmpty()) {
                    g.drawString(instruction, panelX + 20, yPos);
                    yPos += 20;
                }
            }
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(COLS * TILE_SIZE + INFO_PANEL_WIDTH, ROWS * TILE_SIZE);
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            new MazeGame();
        });
    }
}